package com.example.bookstore.validators;

import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.application.FacesMessage;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import javax.faces.component.StateHolder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

@FacesValidator(value = "formatValidator")
public class FormatValidator implements Validator, StateHolder {
	// ----------------------------------------------------- Manifest Constants

	/**
	 * <p>
	 * The message identifier of the Message to be created if the validation
	 * fails. The message format string for this message may optionally include
	 * a <code>{0}</code> placeholder, which will be replaced by list of format
	 * patterns.
	 * </p>
	 */
	public static final String FORMAT_INVALID_MESSAGE_ID = "FormatInvalid";
	private ArrayList<String> formatPatternsList = null;

	//
	// General Methods
	//
	/**
	 * <code>|</code> separated String of format patterns that this validator
	 * must match against.
	 */
	private String formatPatterns = null;
	private boolean transientValue = false;

	//
	// Constructors and Initializers
	//
	public FormatValidator() {
		super();
	}

	/**
	 * <p>
	 * Construct a FormatValidator with the specified formatPatterns String.
	 * </p>
	 * 
	 * @param formatPatterns
	 *            <code>|</code> separated String of format patterns that this
	 *            validator must match against.
	 * 
	 */
	public FormatValidator(String formatPatterns) {
		super();
		this.formatPatterns = formatPatterns;
		parseFormatPatterns();
	}

	/**
	 * <p>
	 * Return the format patterns that the validator supports.
	 */
	public String getFormatPatterns() {
		return (this.formatPatterns);
	}

	/**
	 * <p>
	 * Set the format patterns that the validator support..
	 * </p>
	 * 
	 * @param formatPatterns
	 *            <code>|</code> separated String of format patterns that this
	 *            validator must match against.
	 * 
	 */
	public void setFormatPatterns(String formatPatterns) {
		this.formatPatterns = formatPatterns;
		parseFormatPatterns();
	}

	/**
	 * Parses the <code>formatPatterns</code> into validPatterns
	 * <code>ArrayList</code>. The delimiter must be "|".
	 */
	public void parseFormatPatterns() {
		if ((formatPatterns == null) || (formatPatterns.length() == 0)) {
			return;
		}

		if (formatPatternsList != null) {
			// formatPatterns have been parsed already.
			return;
		} else {
			formatPatternsList = new ArrayList<String>();
		}

		StringTokenizer st = new StringTokenizer(formatPatterns, "|");

		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			formatPatternsList.add(token);
		}
	}

	//
	// Methods from Validator
	//
	@Override
	public void validate(FacesContext context, UIComponent component,
			Object toValidate) {
		boolean valid = false;
		String value = null;

		if ((context == null) || (component == null)) {
			throw new NullPointerException();
		}

		if (!(component instanceof UIInput)) {
			return;
		}

		if ((null == formatPatternsList) || (null == toValidate)) {
			return;
		}

		value = toValidate.toString();

		// validate the value against the list of valid patterns.
		Iterator<String> patternIt = formatPatternsList.iterator();

		while (patternIt.hasNext()) {
			valid = isFormatValid((patternIt.next()), value);

			if (valid) {
				break;
			}
		}

		if (!valid) {
			throw new ValidatorException(new FacesMessage("Not Valid"));
		}
	}

	/**
	 * Returns true if the value matches one of the valid patterns.
	 */
	protected boolean isFormatValid(String pattern, String value) {
		boolean valid = true;

		// if there is no pattern to match then value is valid
		if ((pattern == null) || (pattern.length() == 0)) {
			return true;
		}

		// if the value is null or a zero length string return false.
		if ((value == null) || (value.length() == 0)) {
			return false;
		}

		// if the length of the value is not equal to the length of the
		// pattern string then the value is not valid.
		if (value.length() != pattern.length()) {
			return false;
		}

		value = value.trim();

		// rules for matching.
		// 1. if the matching pattern has a "A", then corresponding character
		// in the value should a letter.
		// 2. if the matching pattern has a "9", then corresponding character
		// in the value should a number
		// 3. if the matching pattern has a "#", then corresponding character
		// in the value should a number or a letter
		// 4.. any other character must match literally.
		char[] input = value.toCharArray();
		char[] fmtpattern = pattern.toCharArray();

		for (int i = 0; i < fmtpattern.length; ++i) {
			if (fmtpattern[i] == 'A') {
				if (!(Character.isLetter(input[i]))) {
					valid = false;
				}
			} else if (fmtpattern[i] == '9') {
				if (!(Character.isDigit(input[i]))) {
					valid = false;
				}
			} else if (fmtpattern[i] == '#') {
				if ((!(Character.isDigit(input[i])))
						&& (!(Character.isLetter(input[i])))) {
					valid = false;
				}
			} else {
				if (!(fmtpattern[i] == input[i])) {
					valid = false;
				}
			}
		}

		return valid;
	}

	@Override
	public Object saveState(FacesContext context) {
		Object[] values = new Object[2];
		values[0] = formatPatterns;
		values[1] = formatPatternsList;

		return (values);
	}

	@Override
	public void restoreState(FacesContext context, Object state) {
		Object[] values = (Object[]) state;
		formatPatterns = (String) values[0];
		formatPatternsList = (ArrayList<String>) values[1];
	}

	@Override
	public boolean isTransient() {
		return (this.transientValue);
	}

	@Override
	public void setTransient(boolean transientValue) {
		this.transientValue = transientValue;
	}
}